//----------------------------------------------------------------------------------------------------------------------
// Copyright (c) 2013 James Whitworth
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//----------------------------------------------------------------------------------------------------------------------
#include <sqtest/sqtScriptPredicateHelper.h>
//----------------------------------------------------------------------------------------------------------------------
#include <assert.h>
#include <string.h>
//----------------------------------------------------------------------------------------------------------------------
#include <sqtest/sqtScript.h>
//----------------------------------------------------------------------------------------------------------------------

namespace
{
//----------------------------------------------------------------------------------------------------------------------
// FindPatternInString
//----------------------------------------------------------------------------------------------------------------------
const SQChar *FindPatternInString(
  const SQChar *string,
  size_t        stringLength,
  const SQChar *pattern,
  size_t        patternLength)
{
  for (size_t i = 0; i != stringLength; ++i)
  {
    bool found = true;
    for (size_t j = 0; j != patternLength; ++j)
    {
      if (string[i + j] != pattern[j])
      {
        found = false;
        break;
      }
    }

    if (found)
    {
      return &string[i];
    }
  }

  return nullptr;
}

}

namespace sqt
{
//----------------------------------------------------------------------------------------------------------------------
// ScriptPredicateHelper
//----------------------------------------------------------------------------------------------------------------------
std::string ScriptPredicateHelper::ConvertStringUtf8(const char *input)
{
  return std::string{ input };
}

//----------------------------------------------------------------------------------------------------------------------
std::string ScriptPredicateHelper::ConvertStringUtf8(const wchar_t *inputString)
{
  std::mbstate_t state;
  size_t outputStringLength = 0;
#if defined(_MSC_VER)
  wcsrtombs_s(&outputStringLength, nullptr, 0, &inputString, _TRUNCATE, &state);
#else
  outputStringLength = std::mbsrtowcs(nullptr, &inputString, 0, &state);
#endif

  std::string outputString;
  outputString.resize(outputStringLength);

#if defined(_MSC_VER)
  wcsrtombs_s(nullptr, &outputString[ 0 ], outputStringLength + 1, &inputString, _TRUNCATE, &state);
#else
  std::wcsrtombs(&outputString[ 0 ], &inputString, outputStringLength + 1, &state);
#endif

  return outputString;
}

//----------------------------------------------------------------------------------------------------------------------
bool ScriptPredicateHelper::GetPredicateText1(HSQUIRRELVM vm,
                                              const Script &script,
                                              const size_t line,
                                              std::string *predicateText)
{
  assert(predicateText != nullptr);

  if (!PushPredicateText1(vm, script, line))
  {
    return false;
  }

  const SQChar *string = nullptr;
  sq_getstring(vm, -1, &string);
  *predicateText = ConvertStringUtf8(string);

  return true;
}

//----------------------------------------------------------------------------------------------------------------------
bool ScriptPredicateHelper::GetPredicateText2(HSQUIRRELVM vm,
                                              const Script &script,
                                              const size_t line,
                                              std::string *expectedPredicateText,
                                              std::string *actualPredicateText)
{
  assert(expectedPredicateText != nullptr);
  assert(actualPredicateText != nullptr);

  if (!PushPredicateText2(vm, script, line))
  {
    return false;
  }

  const SQChar *squirrelString = nullptr;

  sq_getstring(vm, -2, &squirrelString);
  *expectedPredicateText = ConvertStringUtf8(squirrelString);

  sq_getstring(vm, -1, &squirrelString);
  *actualPredicateText = ConvertStringUtf8(squirrelString);

  sq_pop(vm, 2);

  return true;
}

//----------------------------------------------------------------------------------------------------------------------
bool ScriptPredicateHelper::PushPredicateText1(HSQUIRRELVM vm, const Script &script, size_t line)
{
  // use this stack info to get the funcname
  SQStackInfos si_0;
  sq_stackinfos(vm, 0, &si_0);

  size_t lineIndex = line - 1;
  if (lineIndex >= script.GetLineCount())
  {
    return false;
  }

  const auto lineString = script.GetLine(lineIndex);
  const auto lineLength = script.GetLineLength(lineIndex);

  const auto functionName = si_0.funcname;
  const auto functionNameLength = scstrlen(functionName);

  // find the arguments to the function
  const auto match = FindPatternInString(lineString, lineLength, functionName, functionNameLength);
  if (match == nullptr)
  {
    return false;
  }

  auto predicateString = match + functionNameLength;
  size_t predicateStringLength = 0;
  while (*predicateString != '(')
  {
    ++predicateString;
  }
  ++predicateString;

  size_t bracketCount = 0;
  for (size_t i = 0, end = lineLength - (predicateString - lineString); i != end; ++i)
  {
    if (predicateString[i] == '(')
    {
      ++bracketCount;
    }
    else if (predicateString[i] == ')')
    {
      if (bracketCount == 0)
      {
        predicateStringLength = i;
        break;
      }

      --bracketCount;
    }
  }

  sq_pushstring(vm, predicateString, predicateStringLength);

  return true;
}

//----------------------------------------------------------------------------------------------------------------------
bool ScriptPredicateHelper::PushPredicateText2(HSQUIRRELVM vm, const Script &script, size_t line)
{
  // use this stack info to get the funcname
  SQStackInfos si_0;
  sq_stackinfos(vm, 0, &si_0);

  const auto lineIndex = line - 1;
  if (lineIndex >= script.GetLineCount())
  {
    return false;
  }

  const auto lineString = script.GetLine(lineIndex);
  const auto lineLength = script.GetLineLength(lineIndex);

  const auto functionName = si_0.funcname;
  const auto functionNameLength = scstrlen(functionName);

  // find the arguments to the function
  const auto match = FindPatternInString(lineString, lineLength, functionName, functionNameLength);
  if (match == nullptr)
  {
    return false;
  }

  auto predicate1String = match + functionNameLength;
  size_t predicate1StringLength = 0;
  while (*predicate1String != '(')
  {
    ++predicate1String;
  }
  ++predicate1String;

  decltype(predicate1String) predicate2String = nullptr;
  size_t predicate2StringLength = 0;

  size_t bracketCount = 0;
  for (size_t i = 0, end = lineLength - (predicate1String - lineString); i != end; ++i)
  {
    if (predicate2String == nullptr)
    {
      ++predicate1StringLength;
    }
    else
    {
      ++predicate2StringLength;
    }

    if (predicate1String[i] == '(')
    {
      ++bracketCount;
    }
    else if (predicate1String[i] == ')')
    {
      if (bracketCount == 0)
      {
        --predicate2StringLength;
        break;
      }
      --bracketCount;
    }
    else if (predicate1String[i] == ',')
    {
      if (bracketCount == 0)
      {
        --predicate1StringLength;
        predicate2String = &predicate1String[i + 1];
      }
    }
  }

  // trim any whitespace
  //
  while (predicate1String[0] == ' ' && predicate1StringLength > 0)
  {
    ++predicate1String;
    --predicate1StringLength;
  }
  while (predicate1String[predicate1StringLength - 1] == ' ' && predicate1StringLength > 0)
  {
    --predicate1StringLength;
  }
  sq_pushstring(vm, predicate1String, predicate1StringLength);

  if (predicate2String != nullptr)
  {
    while (predicate2String[0] == ' ' && predicate2StringLength > 0)
    {
      ++predicate2String;
      --predicate2StringLength;
    }
    while (predicate2String[predicate2StringLength - 1] == ' ' && predicate2StringLength > 0)
    {
      --predicate2StringLength;
    }
    sq_pushstring(vm, predicate2String, predicate2StringLength);
  }
  else
  {
    sq_pushstring(vm, _SC(""), 0);
  }

  return true;
}

} // namespace sqt
